%{ open Ast %}

%token SEMI L_BRACKET R_BRACKET L_PAREN R_PAREN L_BRACE R_BRACE COMMA RANGE HASH INSERT DELETE
%token PLUS MINUS TIMES DIVIDE AND OR ASSIGN INCREMENT DECREMENT PLUSASSIGN MINUSASSIGN
%token TIMESASSIGN DIFFERENCE UNION INTERSECT MAX MIN AVG
%token EQ NEQ LT LEQ GT GEQ
%token RETURN IF ELSE FOR SHOW INT FUNCTION FUN
%token <int> LITERAL
%token <string> ID STR
%token EOF

%nonassoc ELSE
%nonassoc IF
%nonassoc FOR
%nonassoc SHOW
%nonassoc FUNCTION
%nonassoc NOELSE
%nonassoc FUN

%left ASSIGN PLUSASSIGN MINUSASSIGN TIMESASSIGN
%left DIFFERENCE UNION INTERSECTION
%left EQ NEQ
%left LT GT LEQ GEQ
%left PLUS MINUS
%left TIMES DIVIDE
%left AND OR

%start compilationUnit
%type <Ast.compilationUnit> compilationUnit

%%

/* entry point */  
compilationUnit:  
   statementList { List.rev $1} 
  
statementList:
  /* nothing */{[]}
| statementList statementAll {$2 :: $1}  

/* everything in language is a list of statements and/or functions */  
statementAll:
   statement{$1}
 | functionDeclaration  {$1}

/* all the type of statements. Function call is also a statement */   
statement:
   arrayAssignmentExpression SEMI {Expression($1)}
 | simpleAssignmentExpression SEMI{Expression($1)}
 | arraySingleInitialiseExpression SEMI {Expression($1)}
 | arrayMultiInitialiseExpression SEMI {Expression($1)}
 | arraySourceExpression SEMI {Expression($1)} 
 | IF L_PAREN conditionalExpression R_PAREN L_BRACE statementList R_BRACE %prec NOELSE {If($3,List.rev $6,[])}
 | IF L_PAREN conditionalExpression R_PAREN L_BRACE statementList R_BRACE ELSE L_BRACE statementList R_BRACE    {If($3,List.rev $6,List.rev $10)}
 | FOR L_PAREN simpleAssignmentExpression SEMI conditionalExpression SEMI loopExpression R_PAREN L_BRACE statementList R_BRACE {For($3,$5,$7,List.rev $10)}
 | SHOW L_PAREN showExpressionList R_PAREN SEMI {ShowList($3)}
 | functionCall SEMI {$1}
 | functionSimpleAssignment SEMI {$1}
 | functionArrayAssignment SEMI {$1}

showExpressionList:
   showExpression {[$1]}
 | showExpression COMMA showExpressionList {$1::$3}
 
showExpression:
   parameters { Show($1) }
 | STR { ShowStr($1) }

arrayAssignmentExpression:
   arrayExpression ASSIGN valueExpression {ArrayAssignmentExpression($1,$3)}

valueExpression:
   arrayTypeExpression {$1}
 | number {$1}
   
/* represents a 1 or 2 dimensional array */   
arrayExpression:
   L_BRACKET listExpression R_BRACKET addDimensionExpression ID {ArrayExpression($2,$4,$5)}
   
/* represents the array indexes. can be [1],[a],[1,a],[2..5],[1,a,2..b]*/   
listExpression: 
   /* nothing */{[Noexpr]}
 | elementList {List.rev $1}
 
elementList: 
   elements{[$1]}
 | elementList COMMA elements{$3 :: $1}

elements :
   value{$1}
 | value RANGE value{Range($1,$3)}

value:
  SIGN ID {Id($1,$2)}
 | SIGN LITERAL {Literal($1,$2)}

SIGN:
    {SAdd} 
 | PLUS {SAdd}
 | MINUS {SSub} 

/* represents second dimension of 2 dimension array */   
addDimensionExpression:
  {[]}
| L_BRACKET listExpression R_BRACKET{$2}  
    
/* expressions that returns an array */  
arrayTypeExpression:
   arrayExpression{$1}
 | setExpression{$1}
 | MINUS arrayExpression{Minus($2)}

setExpression:
   UNION L_PAREN arrayExpression COMMA arrayExpression R_PAREN {SetExpression(Union,$3,$5)}
 | INTERSECT L_PAREN arrayExpression COMMA arrayExpression R_PAREN{SetExpression(Intersect,$3,$5)}
 | DIFFERENCE L_PAREN arrayExpression COMMA arrayExpression R_PAREN{SetExpression(Difference,$3,$5)}

simpleAssignmentExpression:
   ID ASSIGN numberTypeExpression {SimpleAssignmentExpression($1,$3)}

number:
   numberType {$1}
 | arrayOperation {$1}
   
numberType:
   value {$1}
 | L_PAREN arithmeticExpression R_PAREN  {$2}
   
/* expressions that returns a number i.e. not an array */   
numberTypeExpression:
   arithmeticExpression {$1}
 | numberType {$1}
 | arrayOperationExpression {$1}
   
arrayOperation:   
   PLUS arrayExpression{PlusExpression($2)} 
 | HASH arrayExpression {HashExpression($2)}
   
/* operations on array that returns a single number */   
arrayOperationExpression:
   arrayOperation {$1}  
 | arrayNumber {$1}

/* accessing individual element of an array [2]a, [2][3]a It will always return a number */   
arrayNumber:
  L_BRACKET numberTypeExpression R_BRACKET addDimension ID {ArrayNumber($2,$4,$5)}
  
addDimension:
  {Noexpr}
| L_BRACKET numberTypeExpression R_BRACKET{$2}  

/* will ultimately return a number */  
arithmeticExpression:
   numberTypeExpression PLUS numberTypeExpression   { ArithmeticExpression($1,Add,$3) }
 | numberTypeExpression MINUS numberTypeExpression  { ArithmeticExpression($1,Sub,$3) }
 | numberTypeExpression TIMES numberTypeExpression  { ArithmeticExpression($1,Mul,$3) }
 | numberTypeExpression DIVIDE numberTypeExpression { ArithmeticExpression($1,Div,$3) }

/* initialising a 1 dimension array */   
arraySingleInitialiseExpression:
   arrayInitialiseExpression initialiseList R_BRACE{ArraySingleInitialiseExpression($1,List.rev $2)}

arrayInitialiseExpression:   
   arrayExpression ASSIGN L_BRACE {$1}    

/* list of anything that is a number */   
initialiseList: 
   numberTypeExpression{[$1]}
 | initialiseList COMMA numberTypeExpression {$3 :: $1}

/* initialising a 2 dimension array */   
arrayMultiInitialiseExpression:
   arrayInitialiseExpression mainInitialiseList R_BRACE{ArrayMultiInitialiseExpression($1,List.rev $2)}
   
/* {{},{},{}} */   
mainInitialiseList:
   multiList{[$1]}
 | mainInitialiseList COMMA multiList{$3 :: $1}

multiList:
   L_BRACE initialiseList R_BRACE{List.rev $2}

/* expression that perform operation on array by taking a array and returning the same array after performing operation */   
arraySourceExpression:
   arrayExpression INCREMENT arrayExpression {Concatenate($1,$3)}  
 | arrayExpression INSERT L_BRACE initialiseList R_BRACE {Insert($1,List.rev $4)}
 | arrayExpression DELETE numberTypeExpression {Delete($1,$3)}

   
/* works on any expression that is anumber. #[]a is a number. so this may also come here */   
conditionalExpression:
/*   numberTypeExpression {$1} */
 | numberTypeExpression LT  numberTypeExpression {ConditionalExpression($1,Lt,$3)}
 | numberTypeExpression LEQ numberTypeExpression {ConditionalExpression($1,Leq,$3)}
 | numberTypeExpression GT  numberTypeExpression {ConditionalExpression($1,Gt,$3)}
 | numberTypeExpression GEQ numberTypeExpression {ConditionalExpression($1,Geq,$3)}
 | numberTypeExpression EQ  numberTypeExpression {ConditionalExpression($1,Eq,$3)}
 | numberTypeExpression NEQ numberTypeExpression {ConditionalExpression($1,Neq,$3)}
 | conditionalExpression AND conditionalExpression {ConditionalExpression($1,And,$3)}
 | conditionalExpression OR  conditionalExpression {ConditionalExpression($1,Or,$3)}
   
/* i++ / j-- / ID = NTE   */   
loopExpression:
   ID INCREMENT{Increment($1)}
 | ID DECREMENT{Decrement($1)}
 | simpleAssignmentExpression {$1}

functionCall:
  FUN ID  L_PAREN actualparameterizedExpression R_PAREN {FunctionCall($2,$4)}

actualparameterizedExpression:
   /* nothing */{[]}
 | aparameterList {List.rev $1}
  
aparameterList:
   parameters {[$1]}
 | aparameterList COMMA parameters {$3 :: $1}

parameters:
   value {$1}
 | PLUS arrayExpression{PlusExpression($2)} 
 | HASH arrayExpression {HashExpression($2)}
 | arrayTypeExpression{$1}
 | farithmetics {$1}
 | L_PAREN farithmetics R_PAREN {$2}

farithmetics:
   parameters PLUS parameters   { ArithmeticExpression($1,Add,$3) }
 | parameters MINUS parameters  { ArithmeticExpression($1,Sub,$3) }
 | parameters TIMES parameters  { ArithmeticExpression($1,Mul,$3) }
 | parameters DIVIDE parameters { ArithmeticExpression($1,Div,$3) }


formalparameterizedExpression:
   /* nothing */{[]}
 | fparameterList {List.rev $1}
  
fparameterList:
   fparameters {[$1]}
 | fparameterList COMMA fparameters {$3 :: $1}

fparameters:
   SIGN ID {Id($1,$2)}
 | arrayExpression{$1}

/* all expressions that can be a number or array */   
/*expression:
   numberTypeExpression{$1}
 | setExpression{$1}
 | MINUS arrayExpression{Minus($2)}
  */ 
functionDeclaration:
   FUNCTION ID L_PAREN formalparameterizedExpression R_PAREN L_BRACE statementList functionClose {FunctionDeclaration($2,$4,List.rev $7,$8)}

/* function can return number or array */   
functionClose:
  R_BRACE {Noexpr}
| RETURN parameters SEMI R_BRACE {$2}     

functionSimpleAssignment:
   ID ASSIGN functionCall {FunctionSimpleAssignment($1,$3)}

functionArrayAssignment:
   arrayExpression ASSIGN functionCall {FunctionArrayAssignment($1,$3)}